package com.soo.hoo.core.aspect;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.soo.hoo.core.annotation.OperateLog;
import com.soo.hoo.core.enums.OperateStatusEnum;
import com.soo.hoo.core.enums.OperatorTypeEnum;
import com.soo.hoo.core.event.OperateLogEvent;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Arrays;
import java.util.Map;

/**
 * <p>日志切面</p>
 *
 * @author 杨燚
 * @since 2024/1/9
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<StopWatch> TIME_THREADLOCAL = new TransmittableThreadLocal<>();


    @Before(value = "@annotation(operateLog)")
    public void doBefore(JoinPoint joinPoint, OperateLog operateLog) {
        StopWatch stopWatch = new StopWatch();
        TIME_THREADLOCAL.set(stopWatch);
        stopWatch.start();
    }

    @AfterReturning(pointcut = "@annotation(operateLog)", returning = "obj")
    public void doAfterReturning(JoinPoint joinPoint, OperateLog operateLog, Object obj) {
        handleLog(joinPoint, operateLog, obj, null);
    }


    @AfterThrowing(value = "@annotation(operateLog)", throwing = "throwable")
    public void doAfterThrowing(JoinPoint joinPoint, OperateLog operateLog, Throwable throwable) {
        handleLog(joinPoint, operateLog, null, throwable);
    }

    /**
     * 处理日志
     *
     * @param joinPoint  连接点
     * @param operateLog 日志注解
     * @param obj        响应结果
     * @param throwable  异常
     */
    protected void handleLog(JoinPoint joinPoint, OperateLog operateLog, Object obj, Throwable throwable) {
        try {
            String clientIp = "";
            String operatorUrl = "";
            String requestMethod = "";
            HttpServletRequest request = getRequest();
            if (request != null) {
                clientIp = JakartaServletUtil.getClientIP(request);
                operatorUrl = StrUtil.sub(request.getRequestURI(), 0, 255);
                requestMethod = request.getMethod();
            }
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            StopWatch stopWatch = TIME_THREADLOCAL.get();
            stopWatch.stop();
            Long castTime = stopWatch.getTotalTimeMillis();
            OperateLogEvent logEvent = OperateLogEvent.builder()
                    .title(operateLog.title())
                    .businessType(operateLog.businessType().getCode())
                    .methodName(className + "." + methodName + "()")
                    .requestMethod(requestMethod)
                    .operatorType(OperatorTypeEnum.PC.getCode())
                    .operatorName(StpUtil.getLoginIdAsString())
//                .operatorDept()
                    .operatorUrl(operatorUrl)
                    .operatorIp(clientIp)
//                .operatorLocation()
                    .status(throwable == null ? OperateStatusEnum.SUCCESS.ordinal() : OperateStatusEnum.FAIL.ordinal())
                    .costTime(castTime)
                    .build();
            if (operateLog.storeParam()) {
                logEvent.setOperatorParam(getParam(joinPoint));
            }
            if (operateLog.storeResult()) {
                logEvent.setOperatorResult(JSON.toJSONString(obj));
            }
            SpringUtil.getApplicationContext().publishEvent(logEvent);
        } catch (Exception e) {
            log.error("日志记录异常：{}", e.getMessage());
        } finally {
            TIME_THREADLOCAL.remove();
        }

    }

    /**
     * 获取当前请求对象
     *
     * @return 请求对象
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null) {
            return requestAttributes.getRequest();
        }
        return null;
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint 连接点
     * @return 参数字符串
     */
    private String getParam(JoinPoint joinPoint) {
        HttpServletRequest request = getRequest();
        if (request != null) {
            String requestMethod = request.getMethod();
            Map<String, String> paramMap = JakartaServletUtil.getParamMap(request);
            if (MapUtil.isEmpty(paramMap) &&
                    HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
                return joinPoint.getArgs() == null ? "" : Arrays.toString(joinPoint.getArgs());
            } else {
                return JSON.toJSONString(paramMap);
            }
        }
        return "";
    }

}
